/*
 * TMap Designer - A Thematic Map design software
 * Copyright (C) 2006 World Health Organization
 * http://www.who.int
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package who.tmap.toolkit.impl;

import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.jhlabs.map.proj.WinkelTripelProjection;

import who.tmap.bo.Annotations;
import who.tmap.bo.Attrs;
import who.tmap.bo.ThematicMap;
import who.tmap.bo.cd.CDColorPattern;
import who.tmap.bo.cd.CDProportionalSymbols;
import who.tmap.bo.cd.CDTypes;
import who.tmap.bo.cd.CartographicDisplay;
import who.tmap.bo.cd.point.CDPoint;
import who.tmap.bo.cd.point.CDPointDataDrivenSize;
import who.tmap.bo.cd.point.CDPointDataDrivenSymbol;
import who.tmap.bo.cd.point.CDPointDataDrivenSymbolAndSize;
import who.tmap.common.TMapException;
import who.tmap.toolkit.MappingToolKit;
import who.tmap.toolkit.cdr.CDRPoint;
import who.tmap.toolkit.cdr.CDRepresentation;
import who.tmap.toolkit.cdr.ProportionalSymbolsRepresentation;
import who.tmap.toolkit.cdr.impl.CDRepresentationFactory;
import who.tmap.toolkit.shape.BaseMapShape;
import who.tmap.toolkit.shape.HasTextPropertyShape;
import who.tmap.toolkit.shape.LabelShape;
import who.tmap.toolkit.shape.MapShape;
import who.tmap.toolkit.shape.PolygonShape;
import who.tmap.toolkit.shape.PolygonShapeDisputedBorder;
import who.tmap.toolkit.shape.annotation.AnnotationShape;
import who.tmap.toolkit.shape.annotation.BackgroundImageShape;
import who.tmap.toolkit.shape.annotation.LabelsListShape;
import who.tmap.toolkit.shape.annotation.LegendShape;
import who.tmap.toolkit.shape.annotation.TitleShape;
import who.tmap.toolkit.shape.cdpointsymbol.CDPointSymbol;

public class MappingToolKitPaintHelper {

    private static Logger logger = Logger.getLogger(MappingToolKitPaintHelper.class);
    
    private MappingToolKit mtk;
    private AnnotationsHelper annHelper =new AnnotationsHelper();
    private AttrHelper attrHelper =new AttrHelper();
	private boolean selectedShapesHidden =false;
	
	public MappingToolKitPaintHelper( MappingToolKit mtk ) {
		this.mtk =mtk;
	}
	
	/**
	 * Draw map
	 * @param g
	 */
	public void paintMap( Graphics2D g, List<AnnotationShape> annShapes, Rectangle2D newBounds ) {
		
	    for( MapShape shape : mtk.getShapes() ) {
		
			if ( shape instanceof BaseMapShape && mtk.isMapVisible() ) {
				shape.setZoomFactor( mtk.getZoomFactor() );
				shape.setMapFactor( mtk.getMapFactor() );
				if ( shape instanceof PolygonShape || shape instanceof PolygonShapeDisputedBorder ) {
					shape.setColor( mtk.getThematicMap().getAnnotations().getBaseMapColor() );
				}
				if ( !shape.isSelected() || !selectedShapesHidden ) {
					shape.draw( g );
				}
				
				// first shape
				if ( newBounds.getWidth() == 0 && newBounds.getHeight() == 0 ) {
				    newBounds.setRect( shape.getBounds().getX(), shape.getBounds().getY(),
				            		   shape.getBounds().getWidth(), shape.getBounds().getHeight() );
				    
				} else if ( !newBounds.contains( shape.getBounds() ) ) {
				    newBounds.add( shape.getBounds() );
				}
				
			} else if ( ( shape instanceof AnnotationShape && mtk.isAnnotationsVisible() ) || shape instanceof BackgroundImageShape ) {
				if ( !shape.isSelected() || !selectedShapesHidden ) {
					annShapes.add( (AnnotationShape)shape );
				}
				
				if ( shape instanceof LabelsListShape ) {
					((LabelsListShape)shape).resetLabelShapes();
				}
			}
			
		}		
	}
	
	/**
	 * Draw cartographic display
	 * @param g
	 */
	@SuppressWarnings("unchecked")
	public void paintCD( Graphics2D g ) {
		
		CDRepresentation cdr =null;
		
		if ( mtk.isCartographicDisplayVisible() ) {		
			CartographicDisplay cd =mtk.getThematicMap() != null ? mtk.getThematicMap().getCartographicDisplay() 
																 : null;
			if ( cd != null ) {
				try {
					if ( cd.getType() != CDTypes.POINT ) {
						cdr =CDRepresentationFactory.getInstance().get( cd.getType() );
					} else {
						cdr =CDRepresentationFactory.getInstance().get( ((CDPoint)cd).getCDPointType() );
					}
					
					cdr.init( mtk, cd, mtk.getMapFactor() );
				} catch (TMapException e) {
					logger.error( e );
					e.printStackTrace();
				}
			}
				
			if ( !(cd instanceof CDPoint) ) {
				for( MapShape shape : mtk.getShapes() ) {					
					if ( mtk.isCartographicDisplayVisible() && cdr !=null && shape instanceof PolygonShape ) {
						try {
							if ( !shape.isSelected() || !selectedShapesHidden ) {
								cdr.represent( g, shape );
							}
						} catch (Exception e) {
							logger.error( e );
							e.printStackTrace();
						}				
					}			
				}
				
				if ( cd.getType() == CDTypes.PROPORTIONAL_SYMBOLS ) {
					CDProportionalSymbols cdps =(CDProportionalSymbols)cd;
					ProportionalSymbolsRepresentation psr =(ProportionalSymbolsRepresentation)cdr;
					cdps.setMinVal( psr.getMinVal() );
					cdps.setMaxVal( psr.getMaxVal() );
				}
				
			// Special case for CD Point
			} else {
				MapShape editedShapeSav =mtk.getEditedShape();
				List<Point2D> selectedPoints =new ArrayList<Point2D>();
				
				// Remove previous CD
				List<CDPointSymbol> existingSymbols =new ArrayList<CDPointSymbol>();
				for( MapShape shape : mtk.getShapes() ) {					
					if ( mtk.isCartographicDisplayVisible() && shape instanceof CDPointSymbol ) {
						existingSymbols.add( (CDPointSymbol)shape );
						if ( shape.isSelected() ) {
							selectedPoints.add( ((CDPointSymbol)shape).getPt() );
						}
					}
				}
				mtk.getShapes().removeAll( existingSymbols );
				mtk.getSelectedShapes().removeAll( existingSymbols );
				
				CDPoint cdPoint =(CDPoint)cd;
				
				int inputCoordXColumnIdx =-1, inputCoordYColumnIdx =-1, sizeColumnIdx =-1, 
					descriptorColumnIdx =-1, dataColumnIdx =-1, nbEntries =-1;
	        	try {
	    			inputCoordXColumnIdx =attrHelper.getIdxForAttrName( mtk.getThematicMap().getData().getHeader()
	    															  , cdPoint.getXColumn() );
	    		} catch (Exception e) {
	    		}    
	    		
	        	try {
	    			inputCoordYColumnIdx =attrHelper.getIdxForAttrName( mtk.getThematicMap().getData().getHeader() 
	    															  , cdPoint.getYColumn() );
	    		} catch (Exception e) {
	    		}   
	    		
	        	try {
	        		descriptorColumnIdx =attrHelper.getIdxForAttrName( mtk.getThematicMap().getData().getHeader() 
	    															 , cdPoint.getDescriptorColumn() );
	    		} catch (Exception e) {
	    		}    	    		
	    		
				if ( cdPoint instanceof CDPointDataDrivenSize || cdPoint instanceof CDPointDataDrivenSymbolAndSize ) {
					String sizeColumn =null;
					if ( cdPoint instanceof CDPointDataDrivenSize ) {
						sizeColumn =((CDPointDataDrivenSize)cdPoint).getSizeColumn();
					} else {
						sizeColumn =((CDPointDataDrivenSymbolAndSize)cdPoint).getSizeColumn();
					}
					
					try {
						sizeColumnIdx =attrHelper.getIdxForAttrName( mtk.getThematicMap().getData().getHeader()
																   , sizeColumn );
					} catch (Exception e) {
					}
				}	
				
				if ( cdPoint instanceof CDPointDataDrivenSymbol || cdPoint instanceof CDPointDataDrivenSymbolAndSize ) {
					String dataColumn =null;
					if ( cdPoint instanceof CDPointDataDrivenSymbol ) {
						dataColumn =((CDPointDataDrivenSymbol)cdPoint).getDataColumn();
						nbEntries = ((CDPointDataDrivenSymbol)cdPoint).getEntries().size();
					} else {
						dataColumn =((CDPointDataDrivenSymbolAndSize)cdPoint).getDataColumn();
						nbEntries = ((CDPointDataDrivenSymbolAndSize)cdPoint).getEntries().size();						
					}
					
					try {
						dataColumnIdx =attrHelper.getIdxForAttrName( mtk.getThematicMap().getData().getHeader()
																   , dataColumn );
					} catch (Exception e) {
					}					
				}
	    		
	    		mtk.getThematicMap().getData().setXColumnIdx( inputCoordXColumnIdx );
	    		mtk.getThematicMap().getData().setYColumnIdx( inputCoordYColumnIdx );  
	    		mtk.getThematicMap().getData().setDescriptorColumnIdx( descriptorColumnIdx );  
	    		mtk.getThematicMap().getData().setSizeColumnIdx( sizeColumnIdx );
	    		mtk.getThematicMap().getData().setDataIdx(dataColumnIdx);
				
				CDRPoint cdrPoint =(CDRPoint)cdr;
				
				Attrs attrs =mtk.getThematicMap().getData();
				List<List> rows =attrs.getRows();
				for( List row : rows ) {
					double x =extractDouble( row, attrs.getXColumnIdx() );
					double y =extractDouble( row, attrs.getYColumnIdx() );
					
					if ( x != Double.MIN_VALUE && y != Double.MIN_VALUE ) {
						Point2D pt =new Point2D.Double( x, -y );

						double sizeVal =extractDouble( row, sizeColumnIdx );
						
						String data =null;
						if ( dataColumnIdx != -1 ) {
							data =row.get( dataColumnIdx ).toString();
						}
						
						try {
							cdrPoint.represent( g, pt, selectedPoints.contains( pt ), sizeVal, data );
						} catch (TMapException e) {
							logger.error( e );
							e.printStackTrace();
						}	
					}
				}
				
				if ( nbEntries != -1 ) {
					int nbEntries2 =-1;
					if ( cdPoint instanceof CDPointDataDrivenSymbol ) {
						nbEntries2 = ((CDPointDataDrivenSymbol)cdPoint).getEntries().size();
					} else {
						nbEntries2 = ((CDPointDataDrivenSymbolAndSize)cdPoint).getEntries().size();						
					}
					
					// The # of entries has changed, the cd point panel table model
					// needs to be refreshed
					if ( nbEntries != nbEntries2 ) {
						//refresh table model in the cd point panel !
						logger.info("refresh table model in the cd point panel !");
					}
				}
				
				mtk.setEditedShape( editedShapeSav );
				
			}
		}

	}	
	
	@SuppressWarnings("unchecked")
	private double extractDouble( List row, int idx ) {
		double dbl =Double.MIN_VALUE;
		
		try {
			dbl =((Number)row.get( idx )).doubleValue();
		} catch( Exception e ) {
			try {
				dbl =Double.parseDouble((String)row.get( idx ));
			} catch( Exception e2 ) {
				
			}
		}
		
		return dbl;
	}
	
	/**
	 * Draw annotations
	 * @param g
	 * @param annShapes
	 * @param newBounds
	 */
	@SuppressWarnings("unchecked")
	public void paintAnnotations( Graphics2D g, 
								  List<AnnotationShape> annShapes, 
								  Rectangle2D newBounds,
								  Map<String, Object> annotationContext ) {
		
		if ( mtk.isAnnotationsVisible() ) {
			
			Map data =null;
			ThematicMap thematicMap =mtk.getThematicMap();
			if ( thematicMap != null && thematicMap.getCartographicDisplay() != null ) {
				data =thematicMap.getCartographicDisplay().getPolygonAssociatedData();
			}
			
			Annotations annotations =thematicMap != null ? thematicMap.getAnnotations()
														 : null;
			
			if ( annotations == null ) {
				return;
			}	
			
			List<LabelsListShape> labelsLists =new ArrayList<LabelsListShape>();
			
			TitleShape titleShape =null;
			LegendShape legendShape =null;
			boolean displayTitle =false;
			boolean displayLegend =false;
						
			for( AnnotationShape shape : annShapes ) {
				
				if ( (!(shape instanceof LabelShape) || annotations.isLabelsVisible() ) ) 
				{
					boolean displayAnnotation =true;

					if ( shape instanceof LabelShape ) {
						
						displayAnnotation =processLabelShape( (LabelShape)shape, annotations, data, thematicMap.getCartographicDisplay() );
						
						MapShape associatedMapShape =((LabelShape)shape).getMapShape();
						if (  associatedMapShape != null && associatedMapShape.isSelected() && selectedShapesHidden ) {
							displayAnnotation =false;
						}

					} else if ( shape instanceof TitleShape ) {
						titleShape =(TitleShape)shape;

						displayTitle =processTitleShape( titleShape, annotations );
						
						// display title at the end
						displayAnnotation =false;
						
					} else if ( shape instanceof LegendShape ) {
						legendShape =(LegendShape)shape;
						
						displayLegend =processLegendShape( legendShape, annotations );
						
						// display legend at the end (just before title)
						displayAnnotation =false;
					
					} else if ( shape instanceof LabelsListShape ) {
						if ( ((LabelsListShape)shape).isVisible() ) {
							labelsLists.add( (LabelsListShape)shape );
						}
						
						// display later, before legend
						displayAnnotation =false;
						
					} else if ( shape instanceof BackgroundImageShape ) {	
						// first thing displayed
						displayAnnotation =false;
					}
					
					if ( displayAnnotation ) {	
						if ( !shape.isSelected() || !selectedShapesHidden ) {
							paintAnnotationsShape( g, newBounds, shape, annotationContext );
						}
					}
				} 
				
			}
			
			// Labels lists are painted just before the legend
			if ( annotations.isLabelsVisible() ) {
				for( LabelsListShape labelsList : labelsLists ) {
					paintAnnotationsShape( g, newBounds, labelsList, null );
				}
			}
			
			// Legend is painted last, just before title 
			// so it is on top of every other shapes (except title)
			if ( legendShape != null && displayLegend ) {
				paintAnnotationsShape( g, newBounds, legendShape, annotationContext );				
			}			
			
			// Title is painted last, so it is on top of every other shapes
			if ( titleShape != null && displayTitle ) {
				paintAnnotationsShape( g, newBounds, titleShape, annotationContext );				
			}
		}

	}	
	
	/**
	 * Label processing
	 * @param label
	 * @param annotations
	 * @param data
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private boolean processLabelShape( LabelShape label, Annotations annotations, Map data, CartographicDisplay cd ) {
		PolygonShape polygon =(PolygonShape)label.getMapShape();
		
		label.setText( cd instanceof CDPoint || polygon.getLabelText() == null ? polygon.getName()
											 								   : polygon.getLabelText() );
		label.applyStyle( annotations.getLabelsStyle() );
		
		double dbl =Double.MIN_VALUE;
		if ( data != null ) {
			Object obj =data.get( polygon );
			String val =obj != null ? obj.toString() : "";
			if ( val != null ) {
		        try {
		        	dbl =Integer.parseInt( val );
		        } catch( Exception e ) {
		        	try {
		        		// then with a double
		        		dbl =Double.parseDouble( val );
		        	} catch( Exception e2 ) {
		        		dbl =Double.MIN_VALUE;
		        	}
		        }
			}
		}
		
		boolean display =label.getText() != null && label.isVisible() && !"".equals( label.getText() )
						&& ( !annotations.isLabelsVisibleIfDataExists() || data == null || data.get( polygon ) != null )
						&& ( annotations.getLabelsVisibleCondition() == Double.MAX_VALUE 
								|| data == null 
								|| ( data.get( polygon ) != null 
										&& dbl > annotations.getLabelsVisibleCondition() ) );
		
		// The label is part of a label list
		if ( display && label.getLabelsList() != null ) {
			Map<String, LabelShape> labelShapes =label.getLabelsList().getLabelShapes();
			
			labelShapes.put( label.getText(), label );
			
			// The label list will display the label
			display =false;
		}
		
		return display;

	}
	
	/**
	 * Special processing for title
	 * @param titleShape
	 * @param annotations
	 * @return
	 */
	private boolean processTitleShape( TitleShape titleShape, Annotations annotations ) {
		titleShape.setText( annotations.getTitle() );
		titleShape.setVisible( annotations.isTitleVisible() );
		titleShape.setBackgroundTransparent( annotations.isTitleBackgroundTransparent() );
		titleShape.applyStyle( annotations.getTitleStyle() );
		
		if ( annotations.getTitlePt() == null ) {
			titleShape.setPt( null );
		} else if ( titleShape.getPt() == null ) {
			titleShape.setPt( (Point2D)annotations.getTitlePt() );
		}
		
		boolean displayTitle =titleShape.isVisible() 
							&& titleShape.getText() != null 
							&& !"".equals( titleShape.getText() );
		
		if ( displayTitle ) {
			if ( titleShape.getPt() == null ) {	
				// Set the title at top left corner
				titleShape.setPt( new Point2D.Double( mtk.getMinX() + ( (mtk.getMaxX()-mtk.getMinX())*0.1d ), 
													  (mtk.getMaxY() + ( (mtk.getMinY()-mtk.getMaxY())*0.1d ))*-1) );
				
				if ( annotations.getTitlePt() == null ) {
					annotations.setTitlePt( (Point2D)titleShape.getPt() );
				}
			
			}
		} else {
			titleShape.setPt( null );
		}
		
		return displayTitle;
	}
	
	/**
	 * Special processing for legend
	 * @param legendShape
	 * @param annotations
	 * @return
	 */
	private boolean processLegendShape( LegendShape legendShape, Annotations annotations ) {
		boolean displayLegend =false;
		
		if ( annotations.getLegendPt() == null ) {
			legendShape.setPt( null );
		} else if ( legendShape.getPt() == null ) {
			legendShape.setPt( (Point2D)annotations.getLegendPt() );
		}		
		
		legendShape.setText( annotations.getLegendText() );
		
		legendShape.setCartographicDisplay( mtk.getThematicMap().getCartographicDisplay() );
		
		if ( legendShape.getCartographicDisplay() instanceof CDColorPattern && annotations.getColorPatternLegend() == null ) {
			CDColorPattern cdcp =(CDColorPattern)legendShape.getCartographicDisplay();
			new AnnotationsHelper().applyRanges( cdcp.getRanges() != null ? cdcp.getRanges().size() : 0, annotations, cdcp.getClassification()  );
		}
		
		legendShape.setBackgroundTransparent( annotations.isLegendBackgroundTransparent() );
		
		legendShape.setColorPatternLegend( annotations.getColorPatternLegend() );
		legendShape.setColorPatternRoundRangesBoundaries( annotations.isColorPatternRoundRangesBoundaries() );
		legendShape.applyStyle( annotations.getLegendStyle() );
		legendShape.setProportionalSymbolsRoundValues( annotations.isProportionalSymbolsRoundValues() );
		
		displayLegend =annotations.isLegendVisible() && legendShape.getCartographicDisplay() != null;
		
		if ( displayLegend && legendShape.getPt() == null ) {
			legendShape.setPt( new Point2D.Double( 
								mtk.getMinX() + ( (mtk.getMaxX()-mtk.getMinX())*0.1d ), 
								(mtk.getMaxY() + ( (mtk.getMinY()-mtk.getMaxY())*0.4d ))*-1) 
							  );
			
			if ( annotations.getLegendPt() == null ) {
				annotations.setLegendPt( (Point2D)legendShape.getPt() );
			}			
		}
		
		return displayLegend;
	}

	
	/**
	 * Display an annotation shape
	 * @param g
	 * @param newBounds
	 * @param shape
	 */
	private void paintAnnotationsShape( Graphics2D g, 
										Rectangle2D newBounds, 
										MapShape shape,
										Map<String, Object> annotationContext ) {
		shape.setZoomFactor( mtk.getZoomFactor() );
		shape.setMapFactor( mtk.getMapFactor() );
		((AnnotationShape)shape).setBackgroundColor( mtk.getThematicMap().getAnnotations() != null ? mtk.getThematicMap().getAnnotations().getBackgroundColor() 
																								   : mtk.getBackgroundColor() );
		
		String textSav ="";
		Annotations ann =mtk.getThematicMap().getAnnotations() != null ? mtk.getThematicMap().getAnnotations() 
																	   : null;
		List<String> cpLegendSav =null;
		HasTextPropertyShape textAnnShape =null;
		
		// It's more efficient to do the swap in here instead of
		// doing it inside the mapShape.draw() method
		if ( annotationContext != null && shape instanceof HasTextPropertyShape 
									   && !(shape instanceof LabelShape) ) {
			textAnnShape =(HasTextPropertyShape)shape;
			textSav =new String( textAnnShape.getText() );
			textAnnShape.setText( 
					annHelper.replaceAnnotations( annotationContext, textAnnShape.getText() ) );
			
			if ( shape instanceof LegendShape && ann.getColorPatternLegend() != null ) {
				cpLegendSav =new ArrayList<String>();
				for( String legend : ann.getColorPatternLegend() ) {
					cpLegendSav.add( legend );
				}
				
				ann.getColorPatternLegend().clear();
				for( String legend : cpLegendSav ) {
					ann.getColorPatternLegend().add( new String( annHelper.replaceAnnotations( annotationContext, legend ) ) );
					logger.info( legend );
				}
			}
		}
		
		shape.setEditMode( mtk.getEditMode() );
		
		shape.draw( g );
		
		// reset the original text if necessary
		if ( annotationContext != null && shape instanceof HasTextPropertyShape 
									   && !(shape instanceof LabelShape)) {
			textAnnShape.setText( textSav );
			
			if ( shape instanceof LegendShape && cpLegendSav != null ) {
				ann.getColorPatternLegend().clear();
				for( String legend : cpLegendSav ) {
					ann.getColorPatternLegend().add( legend );
				}
			}
		}
		
		addShapeBounds( shape, newBounds );
	}
	
	public void paintBackgroundImage( Graphics2D g, BackgroundImageShape bis, PageFormat pf ) {
		if ( mtk.isAnnotationsVisible() ) {
			double mapWidth =Math.abs( mtk.getMaxX() - mtk.getMinX() );
			double mapHeight =Math.abs( mtk.getMaxY() - mtk.getMinY() );
	
			int pixelDrawWidth =(int)(mapWidth * mtk.getMapFactor() * ( (double)mtk.getZoomFactor() / 100d ));
			double ratio =bis.getImageHeight() != 0 ? (double)bis.getImageWidth() / (double)bis.getImageHeight()
											 		: 1.d;	
			int pixelDrawHeight=(int)((double)pixelDrawWidth / ratio);
			 
			bis.setPixelDrawWidth( pixelDrawWidth );
			bis.setPixelDrawHeight( pixelDrawHeight );		
			
			bis.setZoomFactor( mtk.getZoomFactor() );
			bis.setMapFactor( mtk.getMapFactor() );
			
			Point2D originPt =mtk.convertScreenToMap( new Point2D.Double( 0, 0 ) );
			bis.setPt( originPt );	
			
			bis.setOffsetX( pf == null ? 0 : (int)(pf.getImageableX() ) );
			bis.setOffsetY( pf == null ? 0 : (int)(pf.getImageableY() ) );
			
			bis.draw( g );		
		}
	}	
	
	private void addShapeBounds( MapShape shape, Rectangle2D newBounds ) {
		if ( shape.getBounds() != null ) {
			// first shape
			if ( newBounds.getWidth() == 0 && newBounds.getHeight() == 0 ) {
			    newBounds.setRect( shape.getBounds().getX(), shape.getBounds().getY(),
			            		   shape.getBounds().getWidth(), shape.getBounds().getHeight() );
			    
			} else if ( !newBounds.contains( shape.getBounds() ) ) {
			    newBounds.add( shape.getBounds() );
			}
		}
	}
	
	
	public void projectShapes( Container container ) {

		WinkelTripelProjection proj =new WinkelTripelProjection();
		proj.initialize();
		
		Rectangle2D rect =null;
		
		for( MapShape mapShape : mtk.getShapes() ) {
			if ( mapShape instanceof PolygonShape ) {
				
				GeneralPath newPath =new GeneralPath();
				
				float[] c =new float[6];
				PathIterator it =((PolygonShape)mapShape).getGeneralPath().getPathIterator(null);
				while( !it.isDone() ) {
					int type =it.currentSegment(c);  
					
					Point2D.Double src =new Point2D.Double( c[0], c[1] );
					Point2D.Double tgt =new Point2D.Double();					
					
					tgt =proj.transform( src, tgt );
					
					double tgtX =tgt.getX();
					double tgtY =tgt.getY();
					
					switch (type) {
						case PathIterator.SEG_MOVETO:
							newPath.moveTo( (float)tgtX, (float)tgtY );
							break;
							
						case PathIterator.SEG_LINETO: 
							newPath.lineTo( (float)tgtX, (float)tgtY );	
							break;
								
						case PathIterator.SEG_CLOSE:
							newPath.closePath();
							break;	
					}
							
					if ( rect == null ) {
						rect =new Rectangle2D.Double( tgtX, tgtY, tgtX, tgtY );
					} else {
						rect.add( tgtX, tgtY );
					}
					
					it.next();
				}
				
				((PolygonShape)mapShape).setGeneralPath( newPath );
				
				mapShape.getBounds().setRect( newPath.getBounds2D() );
				
			}
		}
		
		if ( rect != null ) {
			mtk.setMinX( rect.getMinX() );		
			mtk.setMaxX( rect.getMaxX() );
			mtk.setMinY( -rect.getMaxY() );
			mtk.setMaxY( -rect.getMinY() );
		}
		
	}	
	
	public void setSelectedShapesHidden( boolean selectedShapesHidden ) {
		this.selectedShapesHidden =selectedShapesHidden;
	}
	
	public boolean isSelectedShapesHidden() {
		return selectedShapesHidden;
	}
	

}
